﻿/**************************************************************
 * 
 * Copyright © Dmitry Kukushkin <codecity@gmail.com> 2007
 * All Rights Reserved
 * 
 **************************************************************/

using System;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Threading;
using WebMoney.Cryptography;
using System.Text;

namespace WebMoney.SignerFX
{
    /// <summary>
    /// Подпись строки
    /// </summary>
    public sealed class Signer
    {
        delegate void AsyncOperationInvoker(AsyncOperation operationUtil);

        private const int UINT_BYTES = 4;
        private const int USHORT_BYTES = 2;
        private const int VALUE_BYTES = 56;
        private const int HASH_LEN = 16;

        private readonly BKeys _keys;
        private readonly MD4 _md4 = new MD4();
        private readonly ModExp _modExp = new ModExp();

        private bool _useMontgomery = true;

        event EventHandler<SignEventArgs> _signComplete;
        
        /// <summary>
        /// Завершение асинхронного вызова
        /// </summary>
        public event EventHandler<SignEventArgs> SignComplete
        {
            add
            {
                if (null == _signComplete)
                    _signComplete = value;
                else
                    lock (_signComplete)
                        if (null != _signComplete)
                            _signComplete += value;
            }
            remove
            {
                if (null != _signComplete)
                    lock (_signComplete)
                        if (null != _signComplete)
                            _signComplete -= value;
            }
        }

        /// <summary>
        /// Применять вычисление степени по модулю методом Монтгомери
        /// По умолчанию true
        /// </summary>
        public bool UseMontgomery
        {
            set
            {
                _useMontgomery = value;
            }
        }

        /// <summary>
        /// Инициализация объекта Base64 String представлением ключей
        /// </summary>
        /// <param name="keysValue">Представление ключей в формате Base64 String</param>
        public Signer(string keysValue)
            : this(new BKeys(keysValue))
        {
        }

        /// <summary>
        /// Инициализация объектом типа BKeys
        /// </summary>
        /// <param name="keys">Объект BKeys</param>
        public Signer(BKeys keys)
        {
            _keys = keys;
        }

        /// <summary>
        /// Асинхронная версия метода Sign
        /// </summary>
        /// <param name="value">Исходная строка</param>
        public void AsyncSign(string value)
        {
            AsyncSign(value, true);
        }

        /// <summary>
        /// Асинхронная версия метода Sign
        /// </summary>
        /// <param name="value">Исходная строка</param>
        /// <param name="randomEnable">Применять генерацию случайных чисел</param>
        public void AsyncSign(string value, bool randomEnable)
        {
            AsyncOperation operationUtil = AsyncOperationManager.CreateOperation(new object[] { value, randomEnable });
            new AsyncOperationInvoker(longOperation).BeginInvoke(operationUtil, null, null);
        }

        private void longOperation(AsyncOperation operationUtil)
        {
            SendOrPostCallback callback = delegate(object state)
                                              {
                                                  if (null != _signComplete)
                                                    lock (_signComplete)
                                                        if (null != _signComplete)
                                                            _signComplete(this, new SignEventArgs(state as string, state as Exception));
                                              };

            object[] userSuppliedState = operationUtil.UserSuppliedState as object[];

            string signString;
            bool randomEnable = false;

            if (null == userSuppliedState || userSuppliedState.Length < 2)
                signString = null;
            else
            {
                signString = (string) userSuppliedState[0];
                randomEnable = (bool)userSuppliedState[1];
            }

            string digitalSignature;

            try
            {
                digitalSignature = Sign(signString, randomEnable);
            }
            catch(Exception e)
            {
                operationUtil.Post(callback, e);
                return;
            }

            operationUtil.Post(callback, digitalSignature);
        }

        /// <summary>
        /// Получение хеша (подписи) строки
        /// </summary>
        /// <param name="value">Исходная строка</param>
        /// <returns>Хеш-значение</returns>
        public string Sign(string value)
        {
            return Sign(value, true);
        }

        /// <summary>
        /// Получение хеша (подписи) строки
        /// </summary>
        /// <param name="value">Исходная строка</param>
        /// <param name="randomEnable">Применять генерацию случайных чисел</param>
        /// <returns>Хеш-значение</returns>
        public string Sign(string value, bool randomEnable)
        {
            if (string.IsNullOrEmpty(value))
                return null;

            uint[] signHash;

            lock (_md4)
            {
                _md4.Initialize();
                signHash = _md4.GetUint32Hash(value);
            }

            byte[] rndBuffer = new byte[VALUE_BYTES - HASH_LEN];

            // Заполняем массив случайными числами
            if (randomEnable)
            {
                RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
                rng.GetBytes(rndBuffer);
            }

            uint[] hashExtended = new uint[VALUE_BYTES / UINT_BYTES];
            Buffer.BlockCopy(new ushort[] { VALUE_BYTES }, 0, hashExtended, 0, USHORT_BYTES);
            Buffer.BlockCopy(signHash, 0, hashExtended, USHORT_BYTES, HASH_LEN);
            Buffer.BlockCopy(rndBuffer, 0, hashExtended, USHORT_BYTES + HASH_LEN, VALUE_BYTES - USHORT_BYTES - HASH_LEN);

            ushort[] uResult;

            if (_useMontgomery)
                uResult = Montgomery.MontgomeryExponentiationUint16(hashExtended, _keys.ExpKey, _keys.ModKey);
            else
                lock (_modExp)
                {
                    _modExp.Initialize();
                    uResult = _modExp.GetUint16Result(hashExtended, _keys.ExpKey, _keys.ModKey);
                }

            StringBuilder sResult = new StringBuilder();

            int pos = 0;

            for (; pos < uResult.Length; pos++)
                sResult.Append(string.Format(null, "{0:x4}", uResult[pos]));

            while(pos++ <= 32)
                sResult.Append(string.Format(null, "{0:x4}", 0));

            return sResult.ToString();
        }
    }
}